Skip to content

fix(claude-cli): surface is_error result events, not raw JSON dump - #751

Open
HUAN2022A wants to merge 1 commit into
nashsu:mainfrom
HUAN2022A:fix/issue-708-cli-surface-is-error-result-even
Open

fix(claude-cli): surface is_error result events, not raw JSON dump#751
HUAN2022A wants to merge 1 commit into
nashsu:mainfrom
HUAN2022A:fix/issue-708-cli-surface-is-error-result-even

Conversation

@HUAN2022A

Copy link
Copy Markdown

Fixes #708

Problem

When the local Claude Code CLI's OAuth session expires, chat fails with a multi-hundred-character raw stdout JSON dump instead of the friendly "Claude Code CLI is not authenticated" message that already exists for the stderr case.

Root cause

All three mechanisms confirmed in code:

  1. parseLine in src/lib/claude-cli-transport.ts returns null for every "type":"result" event without inspecting is_error / result. But in --output-format stream-json mode the CLI reports fatal errors exactly that way: {"type":"result","subtype":"success","is_error":true,"result":"Failed to authenticate: OAuth session expired..."} (the subtype is misleadingly "success"; is_error is what matters).
  2. The discarded line falls into unparsedLines and surfaces via the generic "couldn't parse" fallback in buildExitError() — the dedicated auth branch never runs because it only checks stderr, which is empty here.
  3. Even if it did run, the regex /unauthenticated|please.*log\s*in|authentication.*failed/i does not match "Failed to authenticate: OAuth session expired..." (reversed word order, "authenticate" vs "authentication").

Fix

  • createClaudeCodeStreamParser() now returns a parser with an optional onFatalError side channel; a result event with is_error: true surfaces obj.result through it instead of being dropped. Successful result events behave exactly as before.
  • streamClaudeCodeCli() captures that text and passes it to buildExitError() as a new optional 4th argument (also used with priority in the "exited successfully but no content" branch).
  • buildExitError() matches auth patterns against stderr + resultError together, adding failed to authenticate / oauth session expired to the regex. Non-auth resultError now wins over the raw stdout dump: the user sees the CLI's own one-line failure instead of a JSON wall.

No Rust changes — the Tauri side only forwards stdout lines verbatim.

Testing

  • 8 new cases in src/lib/__tests__/claude-cli-transport.test.ts: parser (real issue payload signals onFatalError once; missing/blank result does not; successful result unchanged), buildExitError (auth message wins over the JSON dump, reversed phrasing matches, non-auth result rendered as a single clean line), and one end-to-end streamClaudeCodeCli case emitting the is_error result + done.
  • npx vitest run src/lib/__tests__/claude-cli-transport.test.ts: 31 passed (23 existing, no regressions)
  • npm run test:mocks: 132 files / 1884 tests passed
  • npm run typecheck: clean

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Expired OAuth session shows as an unparseable raw JSON blob, not the existing "Claude Code CLI is not authenticated" message

1 participant